import Cesium, { MaterialAppearance, Rectangle } from '../Ces/Cesium';
import Util from '../Core/Util';
import CustomPrimitive from '../Weather/Wind/CustomPrimitive';
import WeUniform from './WeUniform';
import WeWindShaders from './WeWindShaders';
import WeDisplayShaders from './WeDisplayShaders';

// 假设 WeWindShaders 和 WeDisplayShaders 的类型
interface WeWindShaders {
  drawVS: string;
  quadVS: string;
  drawFS: string;
  updateFS: string;
  screenFS: string;
}

interface WeDisplayShaders {
  // 如果有具体的属性，可以在这里添加
}

interface WeWindComputeOptions {
  display: any; // 这里假设 display 类型为 any，实际使用时应替换为具体类型
  dataProvider: any; // 这里假设 dataProvider 类型为 any，实际使用时应替换为具体类型
  ffx?: number;
  speed_factor?: number;
  dropRate?: number;
  dropRateBump?: number;
  pointSize?: number;
  particalScale?: number;
}

class WeWindCompute {
  private _display: any;
  private _ready: boolean;
  private _numParticles: number;
  private screenWidth: number;
  private screenHeight: number;
  private _weUniform2: WeUniform;
  private _weUniformSource: string;
  private _baseShaderSource: string;
  private _created: boolean;
  private _dirty: boolean;
  private particleFramebuffer0: any;
  private particleFramebuffer1: any;
  private screenFramebuffer: any;
  private backgroundFramebuffer: any;
  private updatePartical: CustomPrimitive;
  private drawScreen: CustomPrimitive;
  private drawParticles: CustomPrimitive;
  private _moveEndSubscription: (() => void) | undefined;
  private _viewRectangle: Rectangle | undefined;
  private _viewRectangleAjust: Rectangle | undefined;
  private _lastViewRectangle: Rectangle | undefined;
  private extent: Rectangle;
  private groundPrimitive: any;
  private appearance: MaterialAppearance | undefined;
  private _cloneRectangle: any;

  constructor(options: WeWindComputeOptions) {
    this._display = options.display;
    const dataProvider = options.dataProvider;

    this._ready = true;

    const ffx = options.ffx || 2;

    const res = 64 * ffx;

    this._numParticles = res * res;

    this.screenWidth = 2048 * ffx;
    this.screenHeight = 2048 * ffx;

    const windResWidth = dataProvider._tileWidth;
    const windResHeight = dataProvider._tileHeight;

    const floatUniform = {
      windResWidth,
      windResHeight,
      speedFactor: options.speed_factor || 0.5,
      dropRate: options.dropRate || 0.03,
      dropRateBump: options.dropRateBump || 0.001,
      pointSize: options.pointSize || 4,
      particleStateResolution: 0.0,
      fadeOpacity: 0.8, // how fast the particle trails fade on each frame
      colorize: 0.0
    };

    const vec4Uniform = {
      localExtent: this._display._imageryLayer.rectangle,
    };

    this._weUniform2 = new WeUniform({
      floatUniform,
      vec4Uniform,
      name: 'u_WeUniform2'
    });
    this._weUniformSource = this._weUniform2.getSource();
    this._baseShaderSource = this._display.getFragmentShaderSource(true, true, true);
    this._created = false;
    this._dirty = true;
  }

  get weUniform(): WeUniform {
    return this._weUniform2;
  }

  get particleStateResolution(): number {
    return this._weUniform2.Properties.particleStateResolution;
  }

  set particleStateResolution(value: number) {
    this._weUniform2.Properties.particleStateResolution = value;
  }

  createRendering(context: any): void {
    this.createUpdatePrimitive(context);
    this.createDrawPrimitive(context);
  }

  createUpdatePrimitive(context: any): void {
    const numParticles = this._numParticles;

    const particleState = new Uint8Array(numParticles * 4);

    for (let i = 0; i < particleState.length; i++) {
      particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions
    }

    const particleRes = Math.ceil(Math.sqrt(numParticles));

    const width = particleRes;
    const height = particleRes;

    const particalScale = this._display.particalScale || 1.0;

    this.particleStateResolution = particleRes * particalScale;

    const sampler = new Cesium.Sampler({
      minificationFilter: Cesium.TextureMinificationFilter.NEAREST,
      magnificationFilter: Cesium.TextureMagnificationFilter.NEAREST,
    });

    const particlesTextureOptions = {
      context,
      width,
      height,
      pixelFormat: Cesium.PixelFormat.RGBA,
      pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
      sampler,
      flipY: false,
    };

    this.particleFramebuffer0 = Util.createFramebuffer(
      context,
      Util.createTexture(
        particlesTextureOptions,
        particleState,
      ),
    );

    this.particleFramebuffer1 = Util.createFramebuffer(
      context,
      Util.createTexture(
        particlesTextureOptions,
        particleState,
      ),
    );

    const rawRenderState = Util.createRawRenderState({
      viewport: new Cesium.BoundingRectangle(0, 0, width, height),
      depthTest: { enabled: false },
      depthMask: false,
      blending: { enabled: false },
    });

    const that = this;

    const uniformMap = {
      u_particlesTexture(): any {
        return that.particleFramebuffer1.getColorTexture(0);
      },
      u_rand_seed(): number {
        return Math.random();
      },
    };

    this._weUniform2.setupUniformMap(uniformMap);
    this._display.setupUniformMap(uniformMap);

    //@ts-ignore
    this.updatePartical = new CustomPrimitive({
      commandType: 'Draw',
      geometry: Util.getFullscreenQuad(),
      uniformMap,
      vertexShaderSource: new Cesium.ShaderSource({
        sources: [WeWindShaders.quadVS],
      }),
      fragmentShaderSource: new Cesium.ShaderSource({
        sources: [
          this._weUniformSource,
          this._baseShaderSource,
          WeWindShaders.updateFS
        ],
      }),
      rawRenderState,
      framebuffer: this.particleFramebuffer0,
      autoClear: false,
    });
  }

  createDrawPrimitive(context: any): void {
    const width = this.screenWidth;
    const height = this.screenHeight;

    const emptyPixels = new Uint8Array(width * height * 4);

    const colorTextureOptions = {
      context,
      width,
      height,
      pixelFormat: Cesium.PixelFormat.RGBA,
      pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
      flipY: false,
    };

    this.screenFramebuffer = Util.createFramebuffer(
      context,
      Util.createTexture(colorTextureOptions, emptyPixels),
    );
    this.backgroundFramebuffer = Util.createFramebuffer(
      context,
      Util.createTexture(colorTextureOptions, emptyPixels),
    );

    const rawRenderState = Util.createRawRenderState({
      viewport: new Cesium.BoundingRectangle(0, 0, width, height),
      depthTest: { enabled: false },
      depthMask: false,
      blending: { enabled: false },
    });

    const that = this;

    //@ts-ignore
    this.drawScreen = new CustomPrimitive({
      commandType: 'Draw',
      geometry: Util.getFullscreenQuad(),
      uniformMap: {
        screenTexture(): any {
          return that.backgroundFramebuffer.getColorTexture(0);
        },
        u_WeUniform2(): any {
          return that._weUniform2.u_WeUniform;
        },
      },
      vertexShaderSource: new Cesium.ShaderSource({
        sources: [WeWindShaders.quadVS],
      }),
      fragmentShaderSource: new Cesium.ShaderSource({
        sources: [this._weUniformSource, WeWindShaders.screenFS],
      }),
      rawRenderState,
      framebuffer: this.screenFramebuffer,
      autoClear: false,
    });

    const numParticles = this._numParticles;
    const particleIndices = new Float32Array(numParticles);

    for (let i = 0; i < numParticles; i++) {
      particleIndices[i] = i;
    }

    const geometry = new Cesium.Geometry({
      attributes: new Cesium.GeometryAttributes({
        position: new Cesium.GeometryAttribute({
          componentDatatype: Cesium.ComponentDatatype.FLOAT,
          componentsPerAttribute: 1,
          values: particleIndices,
        }),
      }),
    });

    const uniformMap = {
      u_particlesTexture(): any {
        return that.particleFramebuffer0.getColorTexture(0);
      }
    };

    this._weUniform2.setupUniformMap(uniformMap);
    this._display.setupUniformMap(uniformMap);

    //@ts-ignore
    this.drawParticles = new CustomPrimitive({
      commandType: 'Draw',
      geometry,
      primitiveType: Cesium.PrimitiveType.POINTS,
      uniformMap,
      vertexShaderSource: new Cesium.ShaderSource({
        sources: [this._weUniformSource, WeWindShaders.drawVS],
      }),
      fragmentShaderSource: new Cesium.ShaderSource({
        sources: [
          this._weUniformSource,
          this._baseShaderSource,
          WeWindShaders.drawFS
        ],
      }),
      rawRenderState,
      framebuffer: this.screenFramebuffer,
      autoClear: false,
    });
  }

  updateViewerParameters(frameState: any): void {
    if (!this._moveEndSubscription) {
      const that = this;

      this._moveEndSubscription = frameState.camera.moveEnd.addEventListener(
        () => {
          that._dirty = true;
        },
      );
    }

    if (!Cesium.defined(this._viewRectangle)) {
      this._viewRectangle = new Cesium.Rectangle();
      this._viewRectangleAjust = new Cesium.Rectangle();
      this._lastViewRectangle = new Cesium.Rectangle();
    }
    const viewRectangle = frameState.camera.computeViewRectangle(
      Cesium.Ellipsoid.WGS84, this._viewRectangle,
    );

    if (!viewRectangle) {
      return;
    }

    const height = viewRectangle.height || 0;
    const extendHeight = height > Cesium.Math.PI / 12 ? height / 2 : height / 2;

    const south = Cesium.Math.clampToLatitudeRange(viewRectangle.south - extendHeight);
    const north = Cesium.Math.clampToLatitudeRange(viewRectangle.north + extendHeight);

    const viewRectangleAjust = Cesium.Rectangle.fromRadians(viewRectangle.west, south, viewRectangle.east, north, this._viewRectangleAjust);

    this.extent = this._display._imageryLayer.rectangle;

    const viewRectangleWind = Cesium.Rectangle.intersection(this.extent, viewRectangleAjust);

    if (!Cesium.defined(viewRectangleWind) || Cesium.Rectangle.equals(viewRectangleWind, this._lastViewRectangle)) {
      return;
    }

    this._cloneRectangle = Cesium.Rectangle.clone(viewRectangleWind, this._lastViewRectangle);

    this.rebuildRectangle(viewRectangleWind);

    this._weUniform2.Properties.localExtent = viewRectangleWind;

    frameState.commandList.push(
      new Cesium.ClearCommand({
        color: new Cesium.Color(0.0, 0.0, 0.0, 0.0),
        depth: 1.0,
        framebuffer: this.screenFramebuffer,
        pass: Cesium.Pass.OPAQUE,
      }),
    );
    frameState.commandList.push(
      new Cesium.ClearCommand({
        color: new Cesium.Color(0.0, 0.0, 0.0, 0.0),
        depth: 1.0,
        framebuffer: this.backgroundFramebuffer,
        pass: Cesium.Pass.OPAQUE,
      }),
    );
  }

  rebuildRectangle(rectangle: Rectangle): void {
    const geometryInstances = [
      new Cesium.GeometryInstance({
        geometry: new Cesium.RectangleGeometry({
          rectangle,
        }),
      })
    ];

    if (!Cesium.defined(this.appearance)) {
      const that = this;

      this.appearance = new Cesium.MaterialAppearance({
        renderState: {
          depthTest: { enabled: true },
          depthMask: true,
          blending: Cesium.BlendingState.ALPHA_BLEND,
          cull: {
            enabled: true,
            face: Cesium.CullFace.BACK,
          },
        },
        translucent: false,
      });

      this.appearance.material = new Cesium.Material({
        fabric: {
          uniforms: {
            image: that.screenFramebuffer.getColorTexture(0),
          },
          source: `
            czm_material czm_getMaterial(czm_materialInput materialInput) {
                czm_material material = czm_getDefaultMaterial(materialInput);
                vec4 color = texture(image, materialInput.st);
                material.diffuse = color.rgb;
                material.alpha = color.a;
                return material;
            }`,
        },
      });
    }

    if (this.groundPrimitive) {
      this.groundPrimitive = this.groundPrimitive.destroy();
    }
    this.groundPrimitive = new Cesium.GroundPrimitive({
      geometryInstances,
      appearance: this.appearance,
      asynchronous: false,
    });
  }

  update(frameState: any): void {
    if (!this._ready || !frameState.passes.render) {
      return;
    }

    if (!this._created) {
      this._created = true;
      this.createRendering(frameState.context);
    }

    this.drawScreen.update(frameState);
    this.drawParticles.update(frameState);

    const temp1 = this.screenFramebuffer;

    this.screenFramebuffer = this.backgroundFramebuffer;
    this.backgroundFramebuffer = temp1;

    // keep the framebuffers up to date
    this.drawScreen.commandToExecute.framebuffer = this.screenFramebuffer;
    this.drawParticles.commandToExecute.framebuffer = this.screenFramebuffer;

    this.updatePartical.update(frameState);

    const temp = this.particleFramebuffer1;

    this.particleFramebuffer1 = this.particleFramebuffer0;
    this.particleFramebuffer0 = temp;

    // keep the framebuffers up to date
    this.updatePartical.commandToExecute.framebuffer = this.particleFramebuffer0;

    if (this._dirty) {
      this._dirty = false;
      this.updateViewerParameters(frameState);
    }
    if (Cesium.defined(this.groundPrimitive)) {
      this.groundPrimitive.update(frameState);
    }
  }

  destroy(): any {
    if (this.drawScreen) {
      this.drawScreen = this.drawScreen.destroy();
    }
    if (this.drawParticles) {
      this.drawParticles = this.drawParticles.destroy();
    }
    if (this.updatePartical) {
      this.updatePartical = this.updatePartical.destroy();
    }

    if (this.screenFramebuffer) {
      this.screenFramebuffer = this.screenFramebuffer.destroy();
    }
    if (this.backgroundFramebuffer) {
      this.backgroundFramebuffer = this.backgroundFramebuffer.destroy();
    }
    if (this.particleFramebuffer0) {
      this.particleFramebuffer0 = this.particleFramebuffer0.destroy();
    }
    if (this.particleFramebuffer1) {
      this.particleFramebuffer1 = this.particleFramebuffer1.destroy();
    }

    if (this.groundPrimitive) {
      this.groundPrimitive = this.groundPrimitive.destroy();
    }

    if (this._moveEndSubscription) {
      this._moveEndSubscription();
    }

    return Cesium.destroyObject(this);
  }

  isdestroyed(): boolean {
    return false;
  }
}

export default WeWindCompute;
